"""
 针对不同的设备生成的报告信息做对应的处理,前端通过websocket进行数据交互
 根据患者的体检号获取到对应的报告信息
"""
import asyncio
import base64
import json
import logging
import os
import threading
import time
import datetime
import pyodbc
from watchdog.observers import Observer
from src64.watch_handler import MyHandler
from src64.post_report import PostReport
from src64.body_analysis import BodyAnalysisDrawReport
import sqlite3


class Report:
    """
        上传报告到服务器
    """
    upload = None
    user_info = None  # 医生登录信息
    body_analysis = None  # 人体成分分析

    def __init__(self, conf):
        self.conf = conf
        # 获取当前设备的类型信息
        self.REPORT_TYPE = int(conf['base']['type'])
        # 是否开启监听文件夹变动
        self.OPEN_WATCH_DIR = conf['base']['open_watch_dir']
        # 需要监听的文件夹
        self.WATCH_DIR = conf['base']['watch_dir']
        # 是否支持向检测设备中插入患者信息
        self.SET_INFO = conf['base']['set_info']
        # 检查结果图片保存路径
        self.PIC_DIR = conf['pic_dir']['dir']
        # 上传图片的数量
        self.pic_count = int(conf['base']['pic_count'])
        # 体检号
        self.examNo = ''
        # 文件列表
        self.files = []
        self.cursor = None
        # 保存当前长链接session
        self.CONNECTION_SET = set()
        self.send_msg_loop = None  # 新开一个loop,用于在同步代码中调用异步方法
        if conf.has_option('base', 'date_path') and conf['base']['date_path'] == '1':
            date_path = str(datetime.date.today()).replace('-', '')
            self.PIC_DIR = self.PIC_DIR + '\\' + date_path
            self.WATCH_DIR = self.WATCH_DIR + '\\' + date_path
        self.myHandler = MyHandler(report=self)
        self.logger = logging.getLogger('main')
        if conf.has_option('base', 'sex'):
            self.sex = self.conf['base']['sex']

    def connect_adb(self):
        """
            数据库连接信息
        :return:
        """
        global table, pre_table
        if self.conf.has_option(section='sqlserver', option=''):
            """
                如果配置了sqlserver的连接信息,则这里进行链接
            """
            driver = self.conf['sqlserver']['driver']
            host = self.conf['sqlserver']['host']
            database = self.conf['sqlserver']['database']
            username = self.conf['sqlserver']['username']
            pwd = self.conf['sqlserver']['pwd']
            conn = pyodbc.connect(f'DRIVER={driver};SERVER={host};DATABASE={database};UID={username};PWD={pwd}')
            self.cursor = conn.cursor()
        if self.conf.has_option(section='access', option='database'):
            self.logger.info('connection database...')
            """
                如果配置了access 的连接信息,则这里进行链接
            """
            driver = self.conf['access']['driver']
            database = self.conf['access']['database']
            table = self.conf['access']['table']
            pre_table = self.conf['access']['pre_table']
            pwd = self.conf['access']['pwd']
            try:
                con_str = r"DRIVER={Microsoft Access Driver (*.mdb, *.accdb)};" + f"DBQ={database};PWD={pwd}"
                self.conn = pyodbc.connect(con_str)
                self.cursor = self.conn.cursor()
                self.logger.info('connection database success')
                if self.REPORT_TYPE == 8 and self.user_info:
                    self.body_analysis = BodyAnalysisDrawReport(self.conf, self.cursor, self.user_info,self.conn)
            except Exception as e:
                self.logger.error('connection error:{}'.format(e))

        if self.conf.has_option(section='sqlite', option='database'):
            self.logger.info('connection database...')
            """
                如果配置了sqlite 的连接信息,则这里进行链接
            """
            database = self.conf['sqlite']['database']
            table = self.conf['sqlite']['table']
            pre_table = self.conf['sqlite']['pre_table']
            try:
                self.conn = sqlite3.connect(database,check_same_thread=False)
                self.cursor = self.conn.cursor()
                self.logger.info('connection database success')
                if self.REPORT_TYPE == 8 and self.user_info:
                    self.body_analysis = BodyAnalysisDrawReport(self.conf, self.cursor, self.user_info,self.conn)
            except Exception as e:
                self.logger.error('connection error:{}'.format(e))

    def set_user_info(self, user_info):
        """
        设置医生登录信息
        :param user_info:
        :return:
        """
        self.user_info = user_info
        if self.user_info:
            if self.REPORT_TYPE == 8 and not self.body_analysis:
                self.body_analysis = BodyAnalysisDrawReport(self.conf, self.cursor, self.user_info,self.conn)
            if self.body_analysis:
                self.body_analysis.user_info = user_info

    async def send_msg(self, msg):
        """
            发送消息给前端
        :param msg: 需要发送的json数据
        :return:
        """
        self.logger.info('发送消息:{}'.format(msg['data']))
        if self.CONNECTION_SET:
            for c in self.CONNECTION_SET:
                await c.send(json.dumps(msg))

    async def select_info_by_no(self, no):
        """
            根据体检号查询报告信息
        :param no: 体检号
        :return: 报告信息,可以为None
        """
        try:
            if self.cursor:
                self.cursor.execute(f"select * from {table} where 编号='{no}'")
                r = self.cursor.fetchone()
                self.logger.info('查询到数据:{}'.format(r))
                return r
            else:
                self.logger.info('未查询到相关数据,编号:{}'.format(no))
                return ''
        except Exception as e:
            self.logger.error('查询出错:{}'.format(e))
            return ''

    def is_pic(self, src_path):
        type_str = src_path.split('.')[-1].lower()
        if type_str == 'jpeg' or type_str == 'jpg' or type_str == 'png':
            return True
        else:
            return False

    async def find_pic_by_dir(self, examNo):
        """
        在指定的文件夹中获取文件列表
        :param examNo:
        :param websocket:
        :return:
        """
        examNo = str(examNo)
        self.files = os.listdir(self.PIC_DIR)
        if self.files:
            result = list(filter(lambda p: examNo in p and self.is_pic(p), self.files))
            self.logger.info('查询到的文件:{}'.format(result))
            if result:
                pic = ''
                if len(result) > 1:
                    for p in result:
                        pic += ',' + self.PIC_DIR + '\\' + p
                    pic = pic[1:]
                    self.logger.info("图片列表:{}".format(pic))
                else:
                    pic = self.PIC_DIR + '\\' + result[0]
                return pic
        return ''

    async def send_report_info(self, report, no):
        """
         查询本地数据库和图片保存路径,如果有相关报告,则直接发送
        :param report: 查询到的报告数据
        :param no: 体检号
        :return:
        """
        img = await self.find_pic_by_dir(no)
        if not img:
            return
        time.sleep(2)  # 防止前端未渲染完成
        with open(img, 'rb') as f:
            img_data = f.read()
        await self.send_msg(
            {"examNo": no, "type": self.REPORT_TYPE,
             "data": {"value": report[6], "result": report[7].replace('-', '').replace('+', '')},
             "picCount": self.pic_count,
             "pic": base64.b64encode(img_data).decode('utf-8')})

    async def insert_c13_info(self, data, regNo):
        """
        将信息插入碳13数据库中
        :param data:
        :return:
        """

        select_sql = f"select * from {pre_table} where 编号='{regNo}'"

        self.cursor.execute(select_sql)
        r = self.cursor.fetchone()
        self.logger.info("查询结果:{}".format(r))
        main_res = await self.select_info_by_no(regNo)
        if not r and not main_res:
            insert_sql = (f"insert into {pre_table} ([日期],[性别],[年龄],[姓名],[编号],[科室])"
                          f"values ('{time.strftime('%Y/%m/%d %H:%M:%S', time.localtime())}','{'男' if data['sex'] == 1 else '女'}','{data['age']}','{data['patientName']}','{regNo}','体检中心')")
            self.logger.info("数据插入sql:{}".format(insert_sql))
            self.cursor.execute(insert_sql)
            self.cursor.commit()
        if main_res:
            await  self.send_report_info(main_res, regNo)

    async def insert_info_to_db(self, data):
        """
            将用户信息插入到本地检测系统的数据库中
        :param data: 用户信息
        :return:
        """
        self.logger.info("insert_info_to_db:{}".format(self.cursor))
        if self.sex != '3' and self.sex != str(data['sex']):
            # 如果限制性别并且不符合,则不进行插入
            self.logger.info("性别不匹配:{}", format(data['sex']))
            return

        if data and self.cursor:
            regNo = data['registNo']
            if self.REPORT_TYPE == 6:  # 碳13
                await self.insert_c13_info(data, regNo)
            elif self.REPORT_TYPE == 8:  # 人体成分分析
                if not self.body_analysis:
                    self.body_analysis = BodyAnalysisDrawReport(self.conf, self.cursor, self.user_info,self.conn)
                if self.conf.has_option(section='sqlite', option='database'):
                    # 如果是新系统使用sqlite数据库,不插入用户数据,因为插入数据会锁死数据库导致系统异常
                    pass
                    # await self.body_analysis.insert_body_analysis_info(data, regNo)
                else:
                    # 老系统使用定时查询功能
                    await self.body_analysis.insert_body_analysis_info(data, regNo)

    async def insert_info_to_file(self, data):
        """
            将患者信息插入到文件中
            格式: ID     ,姓名     ,身高,体重,性别,出生日期,疾病,部位,医生,测量者,科室,腹围
                123456-7,OMRON TARO,179,65.5,1,1954/2/15,1,4,Dr FORM,,内科,84
        :param data:
        :return:
        """
        try:
            regNO = data['registNo']
            # 这里先查询患者是否有报告,如果有则把报告发出
            pic = await self.find_pic_by_dir(regNO)
            pic_list = pic.split(',')
            if pic and pic_list:
                pic_list.sort(reverse=True)
                if len(pic_list) > 2:
                    # 如果有多张图片,则只取最新两张
                    pic_list = pic_list[:2]
                time.sleep(1)
                for p in pic_list:
                    time.sleep(1)
                    with open(p, 'rb') as f:
                        img_data = f.read()
                    await self.send_msg(
                        {"examNo": regNO, "type": self.REPORT_TYPE,
                         "data": {},
                         "picCount": self.pic_count,
                         "pic": base64.b64encode(img_data).decode('utf-8')})
                return

            file_path = self.conf['base']['file_path'] + '\\' + regNO + '.CSV'
            self.logger.info('写入文件:{}'.format(file_path))
            with open(file_path, 'w', encoding='ANSI') as f:
                f.write(
                    f"{regNO},{data['patientName']},{data['sex'] % 2},{data['birthday'].replace('-', '/')},,,,0,4,,,健康管理中心")

        except Exception as e:
            self.logger.error('写入文件出错:{}'.format(e))

    async def c13_report_deal(self, src_path):
        """
        处理碳13报告回传,因为文件以体检号命名,所以这里直接取文件名称
        :param src_path:
        :return:
        """
        img_path = src_path.split('\\')[-1]
        regNo = img_path.split('.')[0].strip()
        if regNo and len(regNo) > 8:  # 这里在生成图片的时候名字不是体检号,而是后面修改成了体检号,所以这里做个判断
            regNo = regNo[:8]
        self.cursor.execute(f"select * from {table} where 编号='{regNo}'")
        r = self.cursor.fetchone()
        self.logger.info("文件被创建,查询结果:{}".format(r))
        if r:
            time.sleep(int(self.conf['base']['sleep_time']))  # 因为一开始名字不是体检号,所以这里等待2秒钟,再根据体检号去查询报告图片
            src_path = await self.find_pic_by_dir(regNo)
            sex = 1 if r[2] == '男' else 2  # 男1 女2
            no = r[5]  # 体检号
            value = r[6]
            result = r[7]  # 结果,阴性-,阳性+
            if result:
                result = result.replace('-', '').replace('+', '')
            if value:
                value = round(value, 2)
            self.logger.info("医生信息:{}".format(self.user_info))
            if self.upload:
                # 如果开启了上传服务,并且接收到了医生的信息,则直接将报告结果上传到服务器
                await self.upload.post_c13_report(
                    {'no': no, 'sex': sex, 'img': src_path, 'result': result, 'value': value})
            else:
                # 如果没有开启上传服务,则将报告信息发送到前端
                try:
                    with open(src_path, 'rb') as f:
                        img_data = f.read()
                        await self.send_msg(
                            {"examNo": no, "type": int(self.REPORT_TYPE),
                             "data": {"value": value, "result": result},
                             "picCount": self.pic_count,
                             "pic": base64.b64encode(img_data).decode('utf-8')})

                except Exception as e:
                    self.logger.error("发送消息失败:{}".format(e))

    async def arteriosclerosis_report_deal(self, src_path):
        """
        动脉硬化报告回传,因为有两张图片,并且需要区分先后,所以这里拿到文件路径解析出体检号;
        根据体检号再次查询报告目录,如果有两张图片则走上传流程
        :param src_path:
        :return:
        """
        img_name = src_path.split('\\')[-1]
        if img_name:
            reg_no = img_name.split('_')[0]
            if reg_no:
                img_list = (await self.find_pic_by_dir(reg_no)).split(',')
                self.logger.info("图片列表:{}".format(img_list))
                if self.upload and img_list and len(img_list) > 1:
                    await self.upload.post_dm_report({'no': reg_no, 'img': img_list})
                else:
                    self.logger.error('上传报告出错:{}'.format(img_list))

    async def file_created_call_back(self, src_path):
        """
        报告图片创建回调
        :param src_path:
        :return:
        """

        if not self.upload and self.conf['service']['post_report'] == '1' and self.user_info:
            self.upload = PostReport(self.conf, self.user_info)
        self.logger.info("图片路径:{}".format(src_path))
        if self.REPORT_TYPE == 6 and self.cursor:
            # 碳13报告回传
            await self.c13_report_deal(src_path)
        elif self.REPORT_TYPE == 7:
            # 动脉硬化报告回传
            await self.arteriosclerosis_report_deal(src_path)
        elif self.REPORT_TYPE == 8:
            if self.body_analysis:
                await self.body_analysis.get_report_by_path(src_path)
        else:
            try:
                with open(src_path, 'rb') as f:
                    img_data = f.read()
                    await self.send_msg(
                        {"examNo": src_path.split('.')[0], "type": int(self.REPORT_TYPE),
                         "data": {},
                         "picCount": self.pic_count,
                         "pic": base64.b64encode(img_data).decode('utf-8')})
            except Exception as e:
                self.logger.error("发送图片消息失败:{}".format(e))
        return None

    async def watchDir(self):
        """
        监听文件夹变动情况
        :return:
        """
        self.logger.info("开启监听文件夹变动")
        event_handler = self.myHandler
        observer = Observer()
        observer.schedule(event_handler, path=self.WATCH_DIR, recursive=False)
        observer.start()
        try:
            while True:
                time.sleep(1)
        except KeyboardInterrupt:
            observer.stop()
        observer.join()

    def watch_dir_loop(self, loop):
        """
         开启一个新线程监听文件夹变动
        :param loop:
        :return:
        """
        self.logger.info("开启文件监听loop")
        asyncio.set_event_loop(loop)
        loop.run_until_complete(asyncio.gather(self.watchDir()))

    def start(self):
        self.connect_adb()
        if self.OPEN_WATCH_DIR == '1':
            thread_loop = asyncio.new_event_loop()
            t = threading.Thread(target=self.watch_dir_loop, args=(thread_loop,))
            t.daemon = True
            t.start()
